using Agron.Data.Contracts;
using Agron.Services.Contracts;

//using Microsoft.Practices.EnterpriseLibrary.Data;

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.ServiceModel;
using System.Threading;


namespace Agron.Services.Behaviors
{
    /// <summary>
    /// Implementation of the <see cref="ISessionBroker"/> service contract.
    /// </summary>
    [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Single, InstanceContextMode = InstanceContextMode.PerSession)]
    public class SessionBroker : ISessionBroker
    {
        // We need to store the clients staticly...
        private static List<ISessionEvents> _callbackList = new List<ISessionEvents>();

        /// <summary>
        /// Initializes a new instance of the <see cref="SessionBroker"/> class.
        /// </summary>
        public SessionBroker() { }

        #region ISessionBroker Members

        /// <summary>
        /// Joins a user to a session.
        /// </summary>
        /// <param name="sessionId">The session unique identifier.</param>
        /// <param name="userId">The user's unique database identifier.</param>
        /// <param name="internetIPAddress">The internet IP address.</param>
        /// <param name="localIPAddress">The local IP address.</param>
        /// <returns>
        /// One of the <see cref="JoinSessionResult"/> enumeration values.
        /// </returns>
        /// <remarks>This will invoke the <see cref="ISessionEvents.UserJoined"/> procedure.</remarks>
        [OperationBehavior]
        public JoinSessionResult JoinSession(int sessionId, Guid userId, string internetIPAddress, string localIPAddress)
        {
            JoinSessionResult returnValue = JoinSessionResult.None;

            if (null == OperationContext.Current)
                throw new InvalidOperationException(Properties.Resources.SessionBrokerOperationContextIsNull);

            // Retrieve user profile from database
            UserProfile userProfile = new MembershipService().GetUserProfileByUserId(userId);

            // Retrieve session info from database
            Session session = new SchedulerService().GetSessionInfo(sessionId);
            if (null == session)
                return JoinSessionResult.Cancelled;

            //// Log join-operation in database
            //Database database = DatabaseFactory.CreateDatabase("Agron");
            //using (DbCommand command = database.GetStoredProcCommand("agron_SessionMember_Insert"))
            //{
            //    database.AddInParameter(command, "SessionId", DbType.Int32, sessionId);
            //    database.AddInParameter(command, "UserId", DbType.Guid, userId);
            //    database.AddInParameter(command, "InternetAddress", DbType.String, internetIPAddress);
            //    database.AddInParameter(command, "LocalAddress", DbType.String, localIPAddress);

            //    database.ExecuteNonQuery(command);

            //    returnValue = JoinSessionResult.Success;
            //}

            // Get callee's callback instance
            ISessionEvents callback = OperationContext.Current.GetCallbackChannel<ISessionEvents>();

            // Is this a new caller?
            if (!_callbackList.Contains(callback))
                _callbackList.Add(callback);

            // Notify all subscribers about the event in a new thread
            Thread notifyThread = new Thread(new ParameterizedThreadStart(this.AsyncNotifySubscriberUserJoined));
            object[] data = new object[] { session, userProfile, internetIPAddress, localIPAddress };
            notifyThread.Start(data);


            return returnValue;
        }

        /// <summary>
        /// Logs-off a user from a session.
        /// </summary>
        /// <param name="sessionId">The session unique identifier.</param>
        /// <param name="userId">The user's unique database identifier.</param>
        /// <remarks>This will invoke the <see cref="ISessionEvents.UserLeft"/> procedure.</remarks>
        [OperationBehavior]
        public void LeaveSession(int sessionId, Guid userId)
        {
            //Database database = DatabaseFactory.CreateDatabase("Agron");

            //using (DbCommand command = database.GetStoredProcCommand("agron_SessionMember_UpdateLogoff"))
            //{
            //    database.AddInParameter(command, "SessionId", DbType.Int32, sessionId);
            //    database.AddInParameter(command, "UserId", DbType.Guid, userId);

            //    database.ExecuteNonQuery(command);
            //}

            // Get callee's callback instance
            ISessionEvents callback = OperationContext.Current.GetCallbackChannel<ISessionEvents>();

            // Is this a new caller?
            if (_callbackList.Contains(callback))
                _callbackList.Remove(callback);

            // Notify subscribers
            Thread notifyThread = new Thread(new ParameterizedThreadStart(this.AsyncNotifySubscriberUserLeft));
            object[] data = new object[] { sessionId, userId };
            notifyThread.Start(data);
        }

        /// <summary>
        /// Gets a <see cref="IEnumerable{SessionMember}"/> collection with the logged-on members of a specified session.
        /// </summary>
        /// <param name="sessionId">The session unique identifier.</param>
        /// <returns></returns>
        [OperationBehavior]
        public IEnumerable<SessionMember> GetSessionMembers(int sessionId)
        {
            Session session = new SchedulerService().GetSessionInfo(sessionId);
            if (null == session)
                    throw new SessionNotFoundException(Properties.Resources.SessionBrokerSessionDoesNotExist);

            List<SessionMember> dataList = new List<SessionMember>();
            MembershipService membershipService=new MembershipService();
            //Database database = DatabaseFactory.CreateDatabase("Agron");
            //using (DbCommand command = database.GetStoredProcCommand("agron_SessionMember_GetBySessionId"))
            //{
            //    database.AddInParameter(command, "SessionId", DbType.Int32, sessionId);

            //    IDataReader dataReader = database.ExecuteReader(command);
            //    if (null != dataReader)
            //    {
            //        while (dataReader.Read())
            //        {
            //            UserProfile userProfile = membershipService.GetUserProfileByUserId(dataReader.GetGuid(0));

            //            SessionMember sessionMember = new SessionMember();
            //            sessionMember.DisplayName = userProfile.DisplayName;
            //            sessionMember.Email = userProfile.Email;
            //            sessionMember.UserId = userProfile.UserId;
            //            sessionMember.Username = userProfile.Username;
            //            sessionMember.IsOnline = dataReader.GetBoolean(1);
            //            sessionMember.InternetAddress = dataReader.IsDBNull(2) ? null : dataReader.GetString(2);
            //            sessionMember.LocalAddress = dataReader.IsDBNull(3) ? null : dataReader.GetString(3);
            //            sessionMember.IsSessionOwner = (sessionMember.UserId == session.OwnerId);

            //            dataList.Add(sessionMember);
            //        }
            //    }
            //}

            return dataList;
        }

        #endregion

        private void AsyncNotifySubscriberUserJoined(object data)
        {
            object[] args = (object[]) data;
            Session session = (Session) args[0];
            UserProfile userProfile = (UserProfile) args[1];
            string internetAddress = (string) args[2];
            string localAddress = (string) args[3];

            _callbackList.ForEach(delegate(ISessionEvents callback)
            {
                try
                {
                    callback.UserJoined(
                        /*isSessionOwner*/ (userProfile.UserId == session.OwnerId),
                        /*sessionId*/ session.SessionId,
                        /*userProfile*/ userProfile,
                        /*timeStamp*/ DateTime.UtcNow,
                        /*internetIPAddress*/ internetAddress,
                        /*localIPAddress*/ localAddress);
                }
                catch (CommunicationException)
                {
                    // Need to handle orphaned subscriber
                }
                catch
                {
                    // Need to handle global error
                }
            });
        }

        private void AsyncNotifySubscriberUserLeft(object data)
        {
            object[] args = (object[]) data;
            int sessionId = (int) args[0];
            Guid userId = (Guid) args[1];

            _callbackList.ForEach(delegate(ISessionEvents callback)
            {
                try
                {
                    callback.UserLeft(false, sessionId, userId, DateTime.UtcNow);
                }
                catch (CommunicationException)
                {
                    // Need to handle orphaned subscriber
                }
                catch
                {
                    // Need to handle global error
                }
            });
        }
    }
}
